'=====================================================================  	
'
'  File:       IncrementTask.vb
'
'  Summary:    This file contains a Control flow task to increment 
'              an integer variable.
'
'  Date:       06/09/2005
'
'---------------------------------------------------------------------
'  This file is part of the Microsoft SQL Server Code Samples.
'  Copyright (C) Microsoft Corporation.  All rights reserved.
'
'  This source code is intended only as a supplement to Microsoft
'  Development Tools and/or on-line documentation.  See these other
'  materials for detailed information regarding Microsoft code samples.
'
'  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
'  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
'  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
'  PARTICULAR PURPOSE.
'=====================================================================                         

Imports System
Imports System.Xml
Imports System.Threading
Imports System.Runtime.InteropServices
Imports Microsoft.SqlServer.Dts.Runtime


<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")> _
<Guid("9B916A96-98F4-4953-B247-A7C717A89DE2"), _
DtsTask(DisplayName:="IncrementTaskVB", _
Description:="IncrementTask Sample", _
UITypeName:="Microsoft.Samples.SqlServer.Dts.IncrementTaskUI,IncrementTaskVB,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=04df50e9a2a0e558")> _
Public NotInheritable Class IncrementTask
    Inherits Task
    Implements IDTSComponentPersist, IDTSBreakpointSite

    Private initialValueInternal As Integer
    Private loopCountValue As Integer
    Private incrementValueInternal As Integer
    Private postExecutionValue As Integer
    Private suspendRequiredValue As Integer
    Private debugModeValue As Integer
    Private bpm As BreakpointManager
    Private eventInfosValue As EventInfos
    Private onBeforeIncrement As EventInfo
    Private onAfterIncrement As EventInfo

    Private suspended As ManualResetEvent
    Private canExecute As ManualResetEvent

    ' Provided when firing events.
    ' When return variable is set to false event will not be fired again.
    Private bFireOnAfterIncrement As Boolean = True
    Private bFireOnBeforeIncrement As Boolean = True
    Private bFireError As Boolean = True
    Private bFireWarning As Boolean = True

#Region "Ctor"

    Public Sub New()
        Me.suspended = New ManualResetEvent(True)
        Me.canExecute = New ManualResetEvent(True)
    End Sub

#End Region

#Region "Properties"


    Public Property LoopCount() As Integer
        Get
            Return Me.loopCountValue
        End Get
        Set(ByVal value As Integer)
            Me.loopCountValue = value
        End Set
    End Property

    Public Property InitialValue() As Integer
        Get
            Return Me.initialValueInternal
        End Get
        Set(ByVal value As Integer)
            Me.initialValueInternal = value
        End Set
    End Property

    Public Property IncrementValue() As Integer
        Get
            Return Me.incrementValueInternal
        End Get
        Set(ByVal value As Integer)
            Me.incrementValueInternal = value
        End Set
    End Property
#End Region

#Region "Task Base Class overrides"

    ''' <summary>
    ''' Called by the DTS Runtime Engine after creating the task. The EventInfos object is used 
    ''' to declare the custom OnBeforeIncrement and OnAfterIncrement events this task raises during exeuction. 
    ''' </summary>
    ''' <param name="connections">The package's Connections collection.</param>
    ''' <param name="variableDispenser">The System variables visible to the task.</param>
    ''' <param name="events">Used to raise events during execution.</param>
    ''' <param name="log">Used to write log entries.</param>
    ''' <param name="eventInfos">The EventInfos object</param>
    Public Overrides Sub InitializeTask(ByVal connections As Connections, ByVal variableDispenser As VariableDispenser, ByVal events As IDTSInfoEvents, ByVal log As IDTSLogging, ByVal eventInfos As EventInfos, ByVal logEntryInfos As LogEntryInfos, ByVal refTracker As ObjectReferenceTracker)
        If eventInfos Is Nothing Then
            Throw New ArgumentNullException("eventInfos")
        End If

        Me.eventInfosValue = eventInfos
        Dim paramNames(0) As String
        Dim paramTypes() As TypeCode = {TypeCode.Int32}
        Dim paramDescriptions(0) As String

        paramNames(0) = "InitialValue"
        paramDescriptions(0) = "The value before increment."

        Me.eventInfosValue.Add("OnBeforeIncrement", "Fires before the task increments the value.", True, paramNames, paramTypes, paramDescriptions)
        Me.onBeforeIncrement = Me.eventInfosValue("OnBeforeIncrement")

        paramDescriptions(0) = "The value after increment."
        Me.eventInfosValue.Add("OnAfterIncrement", "Fires after the initial value is updated.", True, paramNames, paramTypes, paramDescriptions)
        Me.onAfterIncrement = Me.eventInfosValue("OnAfterIncrement")
    End Sub

    ''' <summary>
    ''' Called by the TaskHost.
    ''' </summary>
    ''' <param name="connections">The package's Connections collection.</param>
    ''' <param name="variableDispenser">The System variables visible to the task.</param>
    ''' <param name="componentEvents">Used to raise events during execution.</param>
    ''' <param name="log">Used to write log entries.</param>
    ''' <param name="transaction">Used to enlist in transactions.</param>
    ''' <returns>Returns a value from the DTSExecResult enumeration indicating execution success or failure.</returns>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")> _
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId:="System.Exception.#ctor(System.String)")> _
    Public Overrides Function Execute(ByVal connections As Connections, ByVal variableDispenser As VariableDispenser, ByVal componentEvents As IDTSComponentEvents, ByVal log As IDTSLogging, ByVal transaction As Object) As DTSExecResult
        If componentEvents Is Nothing Then
            Throw New ArgumentNullException("componentEvents")
        End If

        ' While a task is not executing it is suspended.  Now that we are executing,
        ' change to not suspended.
        ChangeEvent(suspended, False)

        ' Check for a suspend before doing any work in case the suspend and execute calls
        ' were initiated at virtually the same time.
        CheckAndSuspend()

        If componentEvents.FireQueryCancel() = True Then
            Return DTSExecResult.Canceled
        End If

        Me.postExecutionValue = Me.initialValueInternal

        If Me.loopCountValue = 0 Then
            Throw New Exception("LoopCount is zero.")
        End If

        Dim arguments(0) As Object

        For x As Integer = 0 To (Me.loopCountValue)
            Me.CheckAndFireBreakpoint(componentEvents, 1)
            '	Fire the OnBeforeIncrement event
            arguments(0) = Me.postExecutionValue
            If bFireOnBeforeIncrement Then
                componentEvents.FireCustomEvent(Me.onBeforeIncrement.Name, Me.onBeforeIncrement.Description, arguments, Nothing, bFireOnBeforeIncrement)
            End If

            Me.CheckAndFireBreakpoint(componentEvents, 2)
            ' increment/decrement the initial value
            Me.postExecutionValue += Me.incrementValueInternal

            '	Fire the onAfter increment event
            arguments(0) = Me.postExecutionValue
            If bFireOnAfterIncrement Then
                componentEvents.FireCustomEvent(Me.onAfterIncrement.Name, Me.onAfterIncrement.Description, arguments, Nothing, bFireOnAfterIncrement)
            End If
        Next

        ChangeEvent(suspended, True)
        Return DTSExecResult.Success
    End Function


    ''' <summary>
    ''' Called by the TaskHost object. Verifies that the LoopCount, InitialValue, and IncrementValues are set correctly.
    ''' </summary>
    ''' <param name="connections">The package's Connections collection.</param>
    ''' <param name="variableDispenser">The System variables visible to the task.</param>
    ''' <param name="componentEvents">Used to raise events during execution.</param>
    ''' <param name="log">Used to write log entries.</param>
    ''' <returns>Returns a value from the DTSExecResult enumeration indicating validation success or failure.</returns>
    Public Overrides Function Validate(ByVal connections As Connections, ByVal variableDispenser As VariableDispenser, ByVal componentEvents As IDTSComponentEvents, ByVal log As IDTSLogging) As DTSExecResult
        If componentEvents Is Nothing Then
            Throw New ArgumentNullException("componentEvents")
        End If

        If Me.loopCountValue = 0 Then
            If bFireError Then
                componentEvents.FireError(0, "IncrementTask", "LoopCount cannot be zero.", "", 0)
            End If

            Return DTSExecResult.Failure
        ElseIf Me.incrementValueInternal = 0 Then
            If bFireWarning Then
                componentEvents.FireWarning(0, "IncrementTask", "The IncrementValue property is zero.", "", 0)
            End If
        End If

        Return DTSExecResult.Success
    End Function

    ''' <summary>
    ''' Returns the result of the Execution of the task
    ''' property.
    ''' </summary>

    Public Overrides ReadOnly Property ExecutionValue() As Object
        Get
            Return Me.postExecutionValue
        End Get
    End Property
#End Region

#Region "IDTSComponentPersist Members"

    ''' <summary>
    ''' Store the custom properties in the package xml.
    ''' </summary>
    ''' <param name="doc">XmlDocument provided by the DTS Runtime Engine</param>
    ''' <param name="infoEvents">IDTSInfoEvents interface</param>
    Sub SaveToXML(ByVal doc As System.Xml.XmlDocument, ByVal infoEvents As IDTSInfoEvents) Implements IDTSComponentPersist.SaveToXML
        If doc Is Nothing Then
            Throw New ArgumentNullException("doc")
        End If

        Dim taskElement As XmlElement = doc.CreateElement(String.Empty, "IncrementTask", String.Empty)

        '	LoopCount property
        Dim loopCountAttribute As XmlAttribute = doc.CreateAttribute(String.Empty, "LoopCount", String.Empty)

        loopCountAttribute.Value = Me.loopCountValue.ToString(System.Globalization.CultureInfo.InvariantCulture)

        '	InitialValue property
        Dim initialValueAttribute As XmlAttribute = doc.CreateAttribute(String.Empty, "InitialValue", String.Empty)
        initialValueAttribute.Value = Me.initialValueInternal.ToString(System.Globalization.CultureInfo.InvariantCulture)

        '	IncrementValue property
        Dim incrementValueAttribute As XmlAttribute = doc.CreateAttribute(String.Empty, "IncrementValue", String.Empty)
        incrementValueAttribute.Value = Me.incrementValueInternal.ToString(System.Globalization.CultureInfo.InvariantCulture)

        taskElement.Attributes.Append(loopCountAttribute)
        taskElement.Attributes.Append(initialValueAttribute)
        taskElement.Attributes.Append(incrementValueAttribute)

        doc.AppendChild(taskElement)
    End Sub

    ''' <summary>
    ''' Initializes the task from the data stored in the package's XmlDocument.
    ''' </summary>
    ''' <param name="node">The Task's XmlNode</param>
    ''' <param name="infoEvents">IDTSComponentEvents Interface</param>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")> _
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId:="System.Exception.#ctor(System.String)")> _
    Sub LoadFromXML(ByVal node As System.Xml.XmlElement, ByVal infoEvents As IDTSInfoEvents) Implements IDTSComponentPersist.LoadFromXML
        If node Is Nothing Then
            Throw New ArgumentNullException("node")
        End If

        ' This might occur if the task's XML has been modified outside of the Business Intelligence
        ' Or SQL Server Workbenches.
        If node.Name <> "IncrementTask" Then
            Throw New Exception("Unexpected task element when loading task.")
        Else
            Try
                Me.loopCountValue = System.Convert.ToInt32(node.Attributes.GetNamedItem("LoopCount").Value, System.Globalization.CultureInfo.InvariantCulture)
                Me.initialValueInternal = System.Convert.ToInt32(node.Attributes.GetNamedItem("InitialValue").Value, System.Globalization.CultureInfo.InvariantCulture)
                Me.incrementValueInternal = System.Convert.ToInt32(node.Attributes.GetNamedItem("IncrementValue").Value, System.Globalization.CultureInfo.InvariantCulture)
            Catch
                Throw
            End Try
        End If
    End Sub

#End Region

#Region "IDTSBreakpointSite Members"


    Sub AcceptBreakpointManager(ByVal breakpointManager As BreakpointManager) Implements IDTSBreakpointSite.AcceptBreakpointManager
        If breakpointManager Is Nothing Then
            Throw New ArgumentNullException("breakpointManager")
        End If

        Me.bpm = breakpointManager
        Me.bpm.CreateBreakpointTarget(1, "Break when the container receives the OnBeforeIncrement event.")
        Me.bpm.CreateBreakpointTarget(2, "Break when the container receives the OnAfterIncrement event.")
    End Sub

    Property DebugMode() As Boolean Implements IDTSBreakpointSite.DebugMode
        Get
            Return Me.debugModeValue <> 0
        End Get
        Set(ByVal value As Boolean)
            Interlocked.Exchange((Me.debugModeValue), IIf(value, 1, 0)) 'TODO: For performance reasons this should be changed to nested IF statements
        End Set
    End Property
#End Region

#Region "IDTSSuspend Members"


    Property SuspendRequired() As Boolean Implements IDTSSuspend.SuspendRequired
        Get
            Return Me.suspendRequiredValue <> 0
        End Get
        Set(ByVal value As Boolean)
            ' This lock is also taken by Suspend().  Since it is possible for the package to be
            ' suspended and resumed in quick succession, this property put might happen before
            ' the actual Suspend() call.  Without the lock, the Suspend() might reset the canExecute
            ' event after we set it to abort the suspension.
            SyncLock Me
                Interlocked.Exchange((Me.suspendRequiredValue), IIf(value, 1, 0)) 'TODO: For performance reasons this should be changed to nested IF statements

                If Not value Then
                    ChangeEvent(canExecute, True)
                End If
            End SyncLock
        End Set
    End Property

    Sub ResumeExecution() Implements IDTSSuspend.ResumeExecution
        ChangeEvent(canExecute, True)
    End Sub

    Sub SuspendExecution() Implements IDTSSuspend.SuspendExecution
        ' This lock is also taken by the set SuspendRequired method.  It prevents this
        ' call from override an aborted suspension.  See comments in set SuspendRequired.
        SyncLock Me
            ' If a suspend is required, do it.
            If Me.suspendRequiredValue <> 0 Then
                ChangeEvent(canExecute, False)
            End If
        End SyncLock

        ' We can't return from Suspend until the task is "suspended".  This can happen
        ' one of two way: the suspended event occurs, indicating that the execute thread
        ' has suspended or the canExecute flag is set, indicating that a suspend is no
        ' longer required.
        Dim suspendOperationComplete As WaitHandle() = {suspended, canExecute}
        WaitHandle.WaitAny(suspendOperationComplete)
    End Sub

#End Region

#Region "Breakpoint Helper Functions"

    Private Sub CheckAndSuspend()
        ' Loop until we can execute.  The loop is required rather than a simple if
        ' because there is a time between the return from WaitOne and the reset that we
        ' might receive another Suspend call.  Suspend() will see that we are suspended
        ' and return.  So we need to rewait.
        While Not canExecute.WaitOne(0, False)
            ChangeEvent(suspended, True)
            canExecute.WaitOne()
            ChangeEvent(suspended, False)
        End While
    End Sub

    Private Sub CheckAndFireBreakpoint(ByVal events As IDTSComponentEvents, ByVal breakpointID As Integer)
        ' If the breakpoint is enabled, fire it.
        If Me.debugModeValue <> 0 AndAlso Me.bpm.IsBreakpointTargetEnabled(breakpointID) Then
            ' Enter a suspend mode before firing the breakpoint.  Firing the breakpoint
            ' will cause the runtime to call Suspend on this task.  Since we are blocked
            ' on the breakpoint, we are suspended, so indicate that.
            ChangeEvent(suspended, True)
            events.FireBreakpointHit(Me.bpm.GetBreakpointTarget(breakpointID))
            ChangeEvent(suspended, False)
        End If

        ' Check for a suspension for two reasons: 1. If we are at a point where we could
        ' fire a breakpoint, we are at a valid suspend point.  Even if we didn't hit a
        ' breakpoint, the runtime may have called suspend, so check for it.  2. Between
        ' the return from OnBreakpointHit and the reset of the event, it is possible to have
        ' received a suspend call that we returned from because we were suspended.  We need
        ' to be sure it is ok to continue executing now.
        CheckAndSuspend()
    End Sub

    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")> _
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId:="System.Exception.#ctor(System.String)")> _
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")> _
    Shared Sub ChangeEvent(ByVal e As ManualResetEvent, ByVal shouldSet As Boolean)
        If e Is Nothing Then
            Throw New ArgumentNullException("e")
        End If

        Dim succeeded As Boolean

        If shouldSet Then
            succeeded = e.Set()
        Else
            succeeded = e.Reset()
        End If

        If Not succeeded Then
            Throw New Exception("Synchronization object failed.")
        End If
    End Sub
#End Region
End Class
